package org.mobicents.slee.tests.diameter;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;

import javax.naming.Context;
import javax.naming.InitialContext;
import javax.slee.ActivityContextInterface;
import javax.slee.RolledBackContext;
import javax.slee.SbbContext;
import javax.slee.facilities.TimerEvent;
import javax.slee.facilities.TimerFacility;
import javax.slee.serviceactivity.ServiceActivity;
import javax.slee.serviceactivity.ServiceActivityFactory;

import net.java.slee.resource.diameter.base.DiameterAvpFactory;
import net.java.slee.resource.diameter.base.DiameterMessageFactory;
import net.java.slee.resource.diameter.base.DiameterProvider;
import net.java.slee.resource.diameter.base.events.AccountingAnswer;
import net.java.slee.resource.diameter.base.events.DiameterMessage;
import net.java.slee.resource.diameter.base.events.avp.DiameterAvp;
import net.java.slee.resource.diameter.base.events.avp.GroupedAvp;

import org.apache.log4j.Logger;
import org.jdiameter.api.Avp;
import org.jdiameter.api.ResultCode;
import org.mobicents.slee.resource.diameter.base.AccountingServerSessionActivityImpl;

/**
 *
 * DiameterExampleSbb.java
 *
 * <br>Super project:  mobicents
 * <br>11:34:16 PM May 26, 2008
 * <br>
 * @author <a href="mailto:brainslog@gmail.com"> Alexandre Mendonca </a>
 * @author <a href="mailto:baranowb@gmail.com"> Bartosz Baranowski </a>
 * @author Erick Svenson
 */
public abstract class DiameterTestsSbb implements javax.slee.Sbb {

  private static Logger logger = Logger.getLogger( DiameterTestsSbb.class );

  private SbbContext sbbContext = null; // This SBB's context

  private Context myEnv = null; // This SBB's environment

  private DiameterProvider provider = null;

  private DiameterMessageFactory messageFactory = null;
  private DiameterAvpFactory avpFactory = null;

  private TimerFacility timerFacility = null;

  private static class DiameterUser
  {
    String msisdn;
    String name;
    String imsi;
    Double balance;
    Double reserved;
    
    public DiameterUser( String msisdn, String name, String imsi, Double balance, Double reserved )
    {
      this.msisdn = msisdn;
      this.name = name;
      this.msisdn = msisdn;
      this.balance = balance;
      this.reserved = reserved;
    }
  }
  
  private static HashMap<String, DiameterUser> users = new HashMap();
  
  static {
    users.put( "00001000", new DiameterUser("00001000", "Alexandre Mendonca", "00001000", 1000.0, 0.0) );
    users.put( "00001001", new DiameterUser("00001001", "Bartosz Baranowski", "00001001", 100.0, 0.0) );
    users.put( "00001002", new DiameterUser("00001002", "Erick Svensson", "00001002", 0.0, 0.0) );
  }
  
  public void setSbbContext( SbbContext context )
  {
    logger.info( "sbbRolledBack invoked." );

    this.sbbContext = context;

    try
    {
      myEnv = (Context) new InitialContext().lookup( "java:comp/env" );

      provider = (DiameterProvider) myEnv.lookup("slee/resources/diameter-base-ra/provider");
      
      //acif = (DiameterActivityContextInterfaceFactory) myEnv.lookup("slee/resources/diameter-base-ra/acif");
      logger.info( "Got Provider:" + provider );

      messageFactory = provider.getDiameterMessageFactory();
      logger.info( "Got Message Factory:" + provider );

      avpFactory = provider.getDiameterAvpFactory();
      logger.info( "Got AVP Factory:" + provider );

      // Get the timer facility
      timerFacility  = (TimerFacility) myEnv.lookup("slee/facilities/timer");
    }
    catch ( Exception e )
    {
      logger.error( "Unable to set sbb context.", e );
    }
  }

  public void unsetSbbContext()
  {
    logger.info( "unsetSbbContext invoked." );

    this.sbbContext = null;
  }

  public void sbbCreate() throws javax.slee.CreateException
  {
    logger.info( "sbbCreate invoked." );
  }

  public void sbbPostCreate() throws javax.slee.CreateException
  {
    logger.info( "sbbPostCreate invoked." );
  }

  public void sbbActivate()
  {
    logger.info( "sbbActivate invoked." );
  }

  public void sbbPassivate()
  {
    logger.info( "sbbPassivate invoked." );
  }

  public void sbbRemove()
  {
    logger.info( "sbbRemove invoked." );
  }

  public void sbbLoad()
  {
    logger.info( "sbbLoad invoked." );
  }

  public void sbbStore()
  {
    logger.info( "sbbStore invoked." );
  }

  public void sbbExceptionThrown( Exception exception, Object event, ActivityContextInterface activity )
  {
    logger.info( "sbbRolledBack invoked." );
  }

  public void sbbRolledBack( RolledBackContext context )
  {
    logger.info( "sbbRolledBack invoked." );
  }

  protected SbbContext getSbbContext()
  {
    logger.info( "getSbbContext invoked." );

    return sbbContext;
  }

  // ##########################################################################
  // ##                          EVENT HANDLERS                              ##
  // ##########################################################################

  public void onServiceStartedEvent( javax.slee.serviceactivity.ServiceStartedEvent event, ActivityContextInterface aci )
  {
    logger.info( "onServiceStartedEvent invoked." );

    try
    {
      // check if it's my service that is starting
      ServiceActivity sa = ( (ServiceActivityFactory) myEnv.lookup( "slee/serviceactivity/factory" ) ).getActivity();
      if( sa.equals( aci.getActivity() ) )
      {
        logger.info( "################################################################################" );
        logger.info( "##  D I A M E T E R   T E S T   A P P L I C A T I O N   S B B   E N G A G E D ##" );
        logger.info( "################################################################################" );

        messageFactory = provider.getDiameterMessageFactory();
        avpFactory = provider.getDiameterAvpFactory();

        logger.info( "Performing sanity check..." );
        logger.info( "Provider [" + provider + "]" );
        logger.info( "Message Factory [" + messageFactory + "]" );
        logger.info( "AVP Factory [" + avpFactory + "]" );
        //logger.info( "# Check completed. Result: " + ((provider != null ? 1 : 0) + (messageFactory != null ? 1 : 0) + (avpFactory != null ? 1 : 0)) + "/3" );

        logger.info( "Connected to " + provider.getPeerCount() + " peers." );
      }
    }
    catch ( Exception e )
    {
      logger.error( "Unable to handle service started event...", e );
    }
  }

  public void onTimerEvent(TimerEvent event, ActivityContextInterface aci)
  {
    sendAccountingRequest();
  }

  public void onAbortSessionRequest(net.java.slee.resource.diameter.base.events.AbortSessionRequest asr, ActivityContextInterface aci)
  {
    logger.info( "Abort-Session-Request received." );
  }

  public void onAbortSessionAnswer(net.java.slee.resource.diameter.base.events.AbortSessionAnswer asa, ActivityContextInterface aci)
  {
    logger.info( "Abort-Session-Answer received." );
  }

  public void onAccountingRequest(net.java.slee.resource.diameter.base.events.AccountingRequest acr, ActivityContextInterface aci)
  {
    long start = System.currentTimeMillis();
    logger.info( "Accounting-Request received. [" + acr + "]" );

    boolean actAsProxy = false;

    try
    {
      // Are we gonna act as a proxy?
      if(actAsProxy)
      {
        // In here we act as a "proxy". Just for testing we take the original message,
        // replace the Origin/Destination Host/Realm AVPs and send it to the emulator.

        boolean hasDestinationHost = false;
        boolean hasDestinationRealm = false;

        List<DiameterAvp> avps = new ArrayList<DiameterAvp>();

        for(DiameterAvp avp : acr.getAvps())
        {
          switch(avp.getCode())
          {
          case Avp.ORIGIN_HOST:
            avps.add(avpFactory.createAvp(Avp.ORIGIN_HOST, "aaa://127.0.0.1:3868".getBytes() ));
            break;
          case Avp.ORIGIN_REALM:
            avps.add(avpFactory.createAvp(Avp.ORIGIN_REALM, "mobicents.org".getBytes() ));
            break;
          case Avp.DESTINATION_HOST:
            avps.add(avpFactory.createAvp(Avp.DESTINATION_HOST, "aaa://127.0.0.1:13868".getBytes() ));
            hasDestinationHost = true;
            break;
          case Avp.DESTINATION_REALM:
            avps.add(avpFactory.createAvp(Avp.DESTINATION_REALM, "mobicents.org".getBytes() ));
            hasDestinationRealm = true;
            break;
          default:
            avps.add(avp);
          }
        }

        if(!hasDestinationHost)
          avps.add(avpFactory.createAvp(Avp.DESTINATION_HOST, "127.0.0.1".getBytes() ));

        if(!hasDestinationRealm)
          avps.add(avpFactory.createAvp(Avp.DESTINATION_REALM, "mobicents.org".getBytes() ));

        logger.info( "AVPs ==> " + avps );

        DiameterAvp[] avpArray = new DiameterAvp[avps.size()];
        avpArray = avps.toArray(avpArray);

        logger.info( "Creating Custom Message..." );
        DiameterMessage ms = messageFactory.createAccountingRequest(avpArray);
        logger.info( "Created Custom Message[" + ms + "]" );

        logger.info( "Sending Custom Message..." );
        provider.createActivity().sendMessage( ms );
        logger.info( "Sent Custom Message[" + ms + "]" );
      }
      else
      {
        // In here we act as a server...
        int subscriptionIdType = -1;
        String subscriptionIdData = "";
        
        int unitType = -1;
        long valueDigits = -1;
        int exponent = 0;
        
        int requestedAction = -1;
        
        int serviceParameterType;
        int serviceParameterValue;
        int serviceParameterInfo;

        DiameterAvp subscriptionIdAvp = null;
        
        DiameterAvp requestedActionAvp = null;

        if(aci.getActivity() instanceof AccountingServerSessionActivityImpl)
        {
          for(DiameterAvp avp : acr.getAvps())
          {
            switch ( avp.getCode() )
            {
            case SUBSCRIPTION_ID:
            {
              // This should contain a SUBSCRIPTION_ID_TYPE and a SUBSCRIPTION_ID_DATA
              if(avp instanceof GroupedAvp)
              {
                GroupedAvp gAvp = (GroupedAvp)avp;
                
                for(DiameterAvp subAvp : gAvp.getExtensionAvps())
                {
                  switch(subAvp.getCode())
                  {
                  case SUBSCRIPTION_ID_TYPE:
                    subscriptionIdType = subAvp.intValue();
                    break;
                  case SUBSCRIPTION_ID_DATA:
                    subscriptionIdData = subAvp.stringValue();
                    break;
                  }
                }
              }
            }
            break;
            
            case REQUESTED_SERVICE_UNIT:
            {              
              // This should contain a UNIT_TYPE and a UNIT_VALUE
              if(avp instanceof GroupedAvp)
              {
                GroupedAvp gAvp = (GroupedAvp)avp;

                for(DiameterAvp subAvp : gAvp.getExtensionAvps())
                {
                  switch(subAvp.getCode())
                  {
                    case UNIT_TYPE:
                      unitType = subAvp.intValue();
                    break;
                    case UNIT_VALUE:
                    {
                      // This should contain a VALUE_DIGITS
                      if(subAvp instanceof GroupedAvp)
                      {
                        GroupedAvp gSubAvp = (GroupedAvp)subAvp;
                        
                        for(DiameterAvp subSubAvp : gSubAvp.getExtensionAvps())
                        {
                          switch(subSubAvp.getCode())
                          {
                            case VALUE_DIGITS:
                              valueDigits = subSubAvp.longValue();
                            break;
                            case EXPONENT:
                              exponent = subSubAvp.intValue();
                            break;
                          }
                        }
                      }
                      break;
                    }
                  }
                }
              }
            }
            break;

            case REQUESTED_ACTION:
              requestedAction = avp.intValue();
              requestedActionAvp = avp;
            break;
            
            case SERVICE_PARAMETER_TYPE:
              // We can discard this...
            case SERVICE_PARAMETER_VALUE:
              // We can discard this...
            case SERVICE_PARAMETER_INFO:
              // We can discard this...
            default:
                
            }
          }
          
          logger.info( "Subscription-Id-Type: " + subscriptionIdType );
          logger.info( "Subscription-Id-Data: " + subscriptionIdData );
          logger.info( "Unit-Type: " + unitType );
          logger.info( "Value-Digits: " + valueDigits );
          logger.info( "Exponent: " + exponent );
          logger.info( "Requested-Action: " + requestedAction );    
          
          AccountingServerSessionActivityImpl assa = (AccountingServerSessionActivityImpl)aci.getActivity();

          // Aditional AVPs container
          List<DiameterAvp> avps = new ArrayList<DiameterAvp>();

          // By default, let's consider it's OK and create answer with 2001
          AccountingAnswer ans = assa.createAccountAnswer( acr, ResultCode.SUCCESS );          
          
          double chargingValue = valueDigits * Math.pow( 10, exponent );
          
          if(subscriptionIdType == 0 || subscriptionIdType == 1)
          {
            DiameterUser user = null;
            if( (user = users.get( subscriptionIdData )) == null )
            {
              // Not a valid user. Reject it with DIAMETER_END_USER_NOT_FOUND.
              ans = assa.createAccountAnswer( acr, 5241 );

              // Subscription ID
              DiameterAvp subscriptionIdTypeAvp = avpFactory.createAvp( 193, 555, subscriptionIdType );
              DiameterAvp subscriptionIdDataAvp = avpFactory.createAvp( 193, 554, subscriptionIdData );
              avps.add( avpFactory.createAvp( 193, 553, new DiameterAvp[]{subscriptionIdTypeAvp, subscriptionIdDataAvp} ) );
            }
            else if(requestedAction == 0 && user.balance < chargingValue)
            {
              logger.info( "Received Direct Debit Request:" );
              logger.info( "User ID " + subscriptionIdData + " (" + user.name + ")" );
              logger.info( "Current Balance: " + user.balance );
              logger.info( "Charging Value: " + chargingValue );

              // Not able to provide the service. not enough balance.
              ans = assa.createAccountAnswer( acr, 4241 );

              // Subscription ID
              DiameterAvp subscriptionIdTypeAvp = avpFactory.createAvp( 193, 555, subscriptionIdType );
              DiameterAvp subscriptionIdDataAvp = avpFactory.createAvp( 193, 554, subscriptionIdData );
              avps.add( avpFactory.createAvp( 193, 553, new DiameterAvp[]{subscriptionIdTypeAvp, subscriptionIdDataAvp} ) );
            }
            else
            {
              boolean isError = false;
              
              // Refund Account?
              if(requestedAction == 1)
              {
                logger.info( "Received Refund Account Request:" );
                logger.info( "User ID " + subscriptionIdData + " (" + user.name + ")" );
                logger.info( "Old Balance: " + user.balance );
                user.balance += chargingValue;
                logger.info( "New Balance: " + user.balance );
              }
              else if(requestedAction == 0)
              {
                logger.info( "Received Direct Debit Request:" );
                logger.info( "User ID " + subscriptionIdData + " (" + user.name + ")" );
                logger.info( "Old Balance: " + user.balance );
                user.balance -= chargingValue;
                logger.info( "New Balance: " + user.balance );
              }
              else
              {
                logger.warn( "Unknown requested action (" + requestedAction + ")" );

                DiameterAvp failedAvp = avpFactory.createAvp( 0, 279, new DiameterAvp[]{requestedActionAvp} );

                ans = assa.createAccountAnswer( acr, ResultCode.INVALID_AVP_VALUE);

                avps.add( failedAvp );
                
                isError = true;
              }
              
              if(!isError)
              {
                // Subscription ID
                DiameterAvp subscriptionIdTypeAvp = avpFactory.createAvp( 193, 555, subscriptionIdType );
                DiameterAvp subscriptionIdDataAvp = avpFactory.createAvp( 193, 554, subscriptionIdData );
                avps.add( avpFactory.createAvp( 193, 553, new DiameterAvp[]{subscriptionIdTypeAvp, subscriptionIdDataAvp} ) );
  
                // Granted Service Unit
                DiameterAvp unitTypeAvp = avpFactory.createAvp( 193, 611, unitType );
                DiameterAvp valueDigitsAvp = avpFactory.createAvp( 193, 617, valueDigits );
                DiameterAvp unitValueAvp = avpFactory.createAvp( 193, 612, new DiameterAvp[]{valueDigitsAvp} );
                avps.add( avpFactory.createAvp( 193, 602, new DiameterAvp[]{unitTypeAvp, unitValueAvp} ) );
                
                // Cost Information
                DiameterAvp costAvp = avpFactory.createAvp( 193, 603, chargingValue );
                DiameterAvp currencyCodeAvp = avpFactory.createAvp( 193, 544, 978 );
                avps.add( avpFactory.createAvp( 193, 604, new DiameterAvp[]{costAvp, currencyCodeAvp} ) );   
              }
            }
          }

          DiameterAvp[] avpArray = new DiameterAvp[avps.size()];
          avpArray = avps.toArray(avpArray);
          
          ans.setExtensionAvps( avpArray );
          
          logger.info( "Sending Accounting-Answer [" + ans + "]" );

          assa.sendAccountingAnswer( ans );

          logger.info( "Accounting-Answer sent." );
        }
      }
    }
    catch (Exception e)
    {
      logger.error( "", e );
    }

    long end = System.currentTimeMillis();

    logger.info( "Accounting-Request proccessed. [" + (end-start) + "ms]" );
  }

  public void onAccountingAnswer(net.java.slee.resource.diameter.base.events.AccountingAnswer aca, ActivityContextInterface aci)
  {
    logger.info( "Accounting-Answer received." );
  }

  public void onCapabilitiesExchangeRequest(net.java.slee.resource.diameter.base.events.CapabilitiesExchangeRequest cer, ActivityContextInterface aci)
  {
    logger.info( "Capabilities-Exchange-Request received." );
  }

  public void onCapabilitiesExchangeAnswer(net.java.slee.resource.diameter.base.events.CapabilitiesExchangeAnswer cea, ActivityContextInterface aci)
  {
    logger.info( "Capabilities-Exchange-Answer received." );
  }

  public void onDeviceWatchdogRequest(net.java.slee.resource.diameter.base.events.DeviceWatchdogRequest dwr, ActivityContextInterface aci)
  {
    logger.info( "Device-Watchdog-Request received." );
  }

  public void onDeviceWatchdogAnswer(net.java.slee.resource.diameter.base.events.DeviceWatchdogAnswer dwa, ActivityContextInterface aci)
  {
    logger.info( "Device-Watchdog-Answer received." );
  }

  public void onDisconnectPeerRequest(net.java.slee.resource.diameter.base.events.DisconnectPeerRequest dpr, ActivityContextInterface aci)
  {
    logger.info( "Disconnect-Peer-Request received." );
  }

  public void onDisconnectPeerAnswer(net.java.slee.resource.diameter.base.events.DisconnectPeerAnswer dpa, ActivityContextInterface aci)
  {
    logger.info( "Disconnect-Peer-Answer received." );
  }

  public void onReAuthRequest(net.java.slee.resource.diameter.base.events.ReAuthRequest rar, ActivityContextInterface aci)
  {
    logger.info( "Re-Auth-Request received." );
  }

  public void onReAuthAnswer(net.java.slee.resource.diameter.base.events.ReAuthAnswer raa, ActivityContextInterface aci)
  {
    logger.info( "Re-Auth-Answer received." );
  }

  public void onSessionTerminationRequest(net.java.slee.resource.diameter.base.events.SessionTerminationRequest rar, ActivityContextInterface aci)
  {
    logger.info( "Session-Termination-Request received." );
  }

  public void onSessionTerminationAnswer(net.java.slee.resource.diameter.base.events.SessionTerminationAnswer raa, ActivityContextInterface aci)
  {
    logger.info( "Session-Termination-Answer received." );
  }

  public void onErrorAnswer(net.java.slee.resource.diameter.base.events.ErrorAnswer era, ActivityContextInterface aci)
  {
    logger.info( "Error-Answer received." );
  }

  // ##########################################################################
  // ##                           PRIVATE METHODS                            ##
  // ##########################################################################

  private final static int SUBSCRIPTION_ID_TYPE = 555;
  private final static int SUBSCRIPTION_ID_DATA = 554;
  private final static int SUBSCRIPTION_ID = 553;

  private final static int UNIT_TYPE = 611; 
  private final static int VALUE_DIGITS = 617; 
  private final static int UNIT_VALUE = 612; 
  private final static int EXPONENT = 616;
  private final static int REQUESTED_SERVICE_UNIT = 606; 

  private final static int REQUESTED_ACTION = 615; // 0 = Direct Debit, 1 = Refund Account
  
  private final static int SERVICE_PARAMETER_TYPE = 608; 
  private final static int SERVICE_PARAMETER_VALUE = 609; 
  private final static int SERVICE_PARAMETER_INFO = 607;
  
  private void sendAccountingRequest()
  {
    try
    {
      List<DiameterAvp> avps = new ArrayList<DiameterAvp>();

      avps.add(avpFactory.createAvp(Avp.SESSION_ID, "12345".getBytes() ));

      DiameterAvp avpVendorId = avpFactory.createAvp( Avp.VENDOR_ID, 193 );
      DiameterAvp avpAcctApplicationId = avpFactory.createAvp( Avp.ACCT_APPLICATION_ID, 193 );

      avps.add( avpFactory.createAvp( Avp.VENDOR_SPECIFIC_APPLICATION_ID, new DiameterAvp[]{avpVendorId, avpAcctApplicationId} ) );

      avps.add(avpFactory.createAvp(Avp.ORIGIN_HOST, "aaa://127.0.0.1:3868".getBytes() ));
      avps.add(avpFactory.createAvp(Avp.ORIGIN_REALM, "mobicents.org".getBytes() ));

      avps.add(avpFactory.createAvp(Avp.DESTINATION_HOST, "aaa://127.0.0.1:13868".getBytes() ));
      avps.add(avpFactory.createAvp(Avp.DESTINATION_REALM, "mobicents.org".getBytes() ));

      // Subscription ID
      DiameterAvp subscriptionIdType = avpFactory.createAvp( 193, 555, 0 );
      DiameterAvp subscriptionIdData = avpFactory.createAvp( 193, 554, "00001000" );
      avps.add( avpFactory.createAvp( 193, 553, new DiameterAvp[]{subscriptionIdType, subscriptionIdData} ) );

      // Requested Service Unit
      DiameterAvp unitType = avpFactory.createAvp( 193, 611, 2 );
      DiameterAvp valueDigits = avpFactory.createAvp( 193, 617, 10L );
      DiameterAvp unitValue = avpFactory.createAvp( 193, 612, new DiameterAvp[]{valueDigits} );
      avps.add( avpFactory.createAvp( 193, 606, new DiameterAvp[]{unitType, unitValue} ) );

      // Record Number and Type
      avps.add(avpFactory.createAvp(Avp.ACC_RECORD_NUMBER, 0 ));
      avps.add(avpFactory.createAvp(Avp.ACC_RECORD_TYPE, 1 ));

      // Requested action
      avps.add( avpFactory.createAvp( 193, 615, 0 ) );

      // Service Parameter Type
      DiameterAvp serviceParameterType = avpFactory.createAvp( 193, 608, 0 );
      DiameterAvp serviceParameterValue = avpFactory.createAvp( 193, 609, "510" );
      avps.add( avpFactory.createAvp( 193, 607, new DiameterAvp[]{serviceParameterType, serviceParameterValue} ) );

      // Service Parameter Type
      DiameterAvp serviceParameterType2 = avpFactory.createAvp( 193, 608, 14 );
      DiameterAvp serviceParameterValue2 = avpFactory.createAvp( 193, 609, "20" );
      avps.add( avpFactory.createAvp( 193, 607, new DiameterAvp[]{serviceParameterType2, serviceParameterValue2} ) );

      DiameterAvp[] avpArray = new DiameterAvp[avps.size()];
      avpArray = avps.toArray(avpArray);

      logger.info( "Creating Custom Message..." );
      DiameterMessage ms = messageFactory.createAccountingRequest(avpArray);
      logger.info( "Created Custom Message[" + ms + "]" );

      logger.info( "Sending Custom Message..." );
      provider.createActivity().sendMessage( ms );
      logger.info( "Sent Custom Message[" + ms + "]" );
    }
    catch (Exception e)
    {
      logger.error( "", e );
    }
  }

}
